package cn.jingyuan.bee.utils.text;

import cn.jingyuan.bee.utils.NumberUtils;
import cn.jingyuan.bee.utils.StringUtils;

/**
 * 文本相似度计算
 **/
public class TextSimilarity {

    /**
     * 计算相似度，两个都是空串相似度为 1，被认为是相同的串
     *
     * @param strA 字符串 1
     * @param strB 字符串 2
     *
     * @return 相似度
     */
    public static double similar(String strA, String strB) {
        String newStrA, newStrB;
        if (strA.length() < strB.length()) {
            newStrA = removeSign(strB);
            newStrB = removeSign(strA);
        } else {
            newStrA = removeSign(strA);
            newStrB = removeSign(strB);
        }
        // 用较大的字符串长度作为分母，相似子串作为分子计算出字串相似度
        int temp = Math.max(newStrA.length(), newStrB.length());
        if (0 == temp) {
            // 两个都是空串相似度为 1，被认为是相同的串
            return 1;
        }

        int temp2 = longestCommonSubstring(newStrA, newStrB).length();
        return NumberUtils.div(temp2, temp);
    }

    /**
     * 计算相似度百分比
     *
     * @param strA 字符串 1
     * @param strB 字符串 2
     * @param scale 保留小数
     *
     * @return 百分比
     */
    public static String similar(String strA, String strB, int scale) {
        return NumberUtils.formatPercent(similar(strA, strB), scale);
    }

    /**
     * 将字符串的所有数据依次写成一行，去除无意义字符串
     *
     * @param str 字符串
     *
     * @return 处理后的字符串
     */
    private static String removeSign(String str) {
        int length = str.length();
        StringBuilder sb = StringUtils.builder(length);
        // 遍历字符串 str,如果是汉字数字或字母，则追加到 ab 上面
        char c;
        for (int i = 0; i < length; i++) {
            c = str.charAt(i);
            if (!isInvalidChar(c)) {
                sb.append(c);
            }
        }

        return sb.toString();
    }

    /**
     * 判断字符是否为非汉字，数字和字母， 因为对符号进行相似度比较没有实际意义，故符号不加入考虑范围。
     *
     * @param charValue 字符
     *
     * @return true 表示为非汉字，数字和字母，false 反之
     */
    private static boolean isInvalidChar(char charValue) {
        return (charValue >= 0x4E00 && charValue <= 0XFFF) ||
                (charValue >= 'a' && charValue <= 'z') ||
                (charValue >= 'A' && charValue <= 'Z') ||
                (charValue >= '0' && charValue <= '9');
    }

    /**
     * 求公共子串，采用动态规划算法。 其不要求所求得的字符在所给的字符串中是连续的。
     *
     * @param stringA 字符串 1
     * @param stringB 字符串 2
     *
     * @return 公共子串
     */
    private static String longestCommonSubstring(String stringA, String stringB) {
        char[] charsStringA = stringA.toCharArray();
        char[] charsStringB = stringB.toCharArray();
        int m = charsStringA.length;
        int n = charsStringB.length;

        // 初始化矩阵数据,matrix[0][0]的值为 0， 如果字符数组 charsStringA 和 charsStringB 的对应位相同，则 matrix[i][j]的值为左上角的值加 1， 否则，matrix[i][j]的值等于左上方最近两个位置的较大值， 矩阵中其余各点的值为 0.
        int[][] matrix = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (charsStringA[i - 1] == charsStringB[j - 1]) {
                    matrix[i][j] = matrix[i - 1][j - 1] + 1;
                } else {
                    matrix[i][j] = Math.max(matrix[i][j - 1], matrix[i - 1][j]);
                }
            }
        }

        // 矩阵中，如果 matrix[m][n]的值不等于 matrix[m-1][n]的值也不等于 matrix[m][n-1]的值， 则 matrix[m][n]对应的字符为相似字符元，并将其存入 result 数组中。
        char[] result = new char[matrix[m][n]];
        int currentIndex = result.length - 1;
        while (matrix[m][n] != 0) {
            if (matrix[m][n] == matrix[m][n - 1]) {
                n--;
            } else if (matrix[m][n] == matrix[m - 1][n]) {
                m--;
            } else {
                result[currentIndex] = charsStringA[m - 1];
                currentIndex--;
                n--;
                m--;
            }
        }
        return new String(result);
    }

}
